// Go作者版权所有。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

package ld

import (
	"cmd/internal/sys"
	"cmd/link/internal/loader"
	"encoding/binary"
	"errors"
	"log"
	"os"
)

// 如果此平台不支持fallocate，则返回此错误。错误
// 在需要时被忽略，并且EXPUFF写入堆内存。
var errNoFallocate = errors.New("operation not supported")

const outbufMode = 0775

// Extraf是一个缓冲文件编写器。
// None
// 对于cmd/internal/bio中的作者来说，这很相似，但有一些小的差异。
// None
// 首先，它跟踪输出架构并使用它提供
// 恩迪亚助手。
// None
// 其次，它提供了一个非常便宜的抵消计数器，不需要
// 任何系统调用来读取该值。
// None
// 第三，它还映射输出文件（如果可用）。预期用途为：
// -Mmap输出文件
// -写内容
// -可能在输出缓冲区中应用任何编辑
// -可能会将更多内容写入文件。这些写操作在堆中进行
// 将同步到磁盘的备份缓冲区。
// -映射输出文件
// None
// 最后，它提供了一种机制，通过该机制可以对
// 输出文件的写入。这个机制是通过复制一个EXPUFF来实现的，
// 并在线程/goroutine中使用它。
// None
// 平行EXBUFF的用途如下：
// None
// func写入（输出*F）{
// var wg sync.WaitGroup
// 对于i:=0；i<10；i++{
// 工作组.添加（1）
// 视图，错误：=out.view（开始[i]）
// 如果出错！=零{
// None
// 持续
// }
// go func（out*exputf，i int）{
// None
// wg.Done（）
// }（视图一）
// }
// wg.Wait（）
// }
type OutBuf struct {
	arch *sys.Arch
	off  int64

	buf  []byte // mmap输出文件的备份存储
	heap []byte // 非MMAP数据的备份存储

	name   string
	f      *os.File
	encbuf [8]byte // 写入方法使用的临时缓冲区
	isView bool    // 如果从视图（）创建，则为true
}

func (out *OutBuf) Open(name string) error {
	if out.f != nil {
		return errors.New("cannot open more than one file")
	}
	f, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, outbufMode)
	if err != nil {
		return err
	}
	out.off = 0
	out.name = name
	out.f = f
	return nil
}

func NewOutBuf(arch *sys.Arch) *OutBuf {
	return &OutBuf{
		arch: arch,
	}
}

var viewError = errors.New("output not mmapped")

func (out *OutBuf) View(start uint64) (*OutBuf, error) {
	return &OutBuf{
		arch:   out.arch,
		name:   out.name,
		buf:    out.buf,
		heap:   out.heap,
		off:    int64(start),
		isView: true,
	}, nil
}

var viewCloseError = errors.New("cannot Close OutBuf from View")

func (out *OutBuf) Close() error {
	if out.isView {
		return viewCloseError
	}
	if out.isMmapped() {
		out.copyHeap()
		out.purgeSignatureCache()
		out.munmap()
	}
	if out.f == nil {
		return nil
	}
	if len(out.heap) != 0 {
		if _, err := out.f.Write(out.heap); err != nil {
			return err
		}
	}
	if err := out.f.Close(); err != nil {
		return err
	}
	out.f = nil
	return nil
}

// 如果对EXPUFF进行了mmaped，则isMmapped返回true。
func (out *OutBuf) isMmapped() bool {
	return len(out.buf) != 0
}

// Data以字节片的形式返回整个写入的EXBUFF。
func (out *OutBuf) Data() []byte {
	if out.isMmapped() {
		out.copyHeap()
		return out.buf
	}
	return out.heap
}

// copyHeap将堆复制到内存的mmapped部分，如果
// 复制发生。
func (out *OutBuf) copyHeap() bool {
	if !out.isMmapped() { // 仅对MMAFS有价值。
		return false
	}
	if out.isView {
		panic("can't copyHeap a view")
	}

	bufLen := len(out.buf)
	heapLen := len(out.heap)
	total := uint64(bufLen + heapLen)
	if heapLen != 0 {
		if err := out.Mmap(total); err != nil { // Mmap将把out.heap复制到out.buf
			Exitf("mapping output file failed: %v", err)
		}
	}
	return true
}

// MaxExputhFheaplen限制堆区域的增长。
const maxOutBufHeapLen = 10 << 20

// writeLoc确定缓冲区是否已MMAP的写入位置。
// 我们维护两个写缓冲区，一个mmamap部分和一个heap部分
// 写当mmamap部分满时，我们切换堆内存
// 为了写作。
func (out *OutBuf) writeLoc(lenToWrite int64) (int64, []byte) {
	// 看看我们是否有足够的空间。
	bufLen := int64(len(out.buf))
	if out.off+lenToWrite <= bufLen {
		return out.off, out.buf
	}

	// mmaped区域空间不足，请改为写入堆区域。
	heapPos := out.off - bufLen
	heapLen := int64(len(out.heap))
	lenNeeded := heapPos + lenToWrite
	if lenNeeded > heapLen { // 我们需要增加堆存储吗？
		// 堆变量不受互斥锁的保护。现在，如果你想爆炸就爆炸吧
		// 尽量同时使用EXBUFF。（请注意，这可能是可以修复的。）
		if out.isView {
			panic("cannot write to heap in parallel")
		}
		// 看看我们的堆是否会变得太大，如果是的话，把它复制到最后
		// 这是地图区域的一部分。
		if heapLen > maxOutBufHeapLen && out.copyHeap() {
			heapPos -= heapLen
			lenNeeded = heapPos + lenToWrite
			heapLen = 0
		}
		out.heap = append(out.heap, make([]byte, lenNeeded-heapLen)...)
	}
	return heapPos, out.heap
}

func (out *OutBuf) SeekSet(p int64) {
	out.off = p
}

func (out *OutBuf) Offset() int64 {
	return out.off
}

// Write将v的内容写入缓冲区。
func (out *OutBuf) Write(v []byte) (int, error) {
	n := len(v)
	pos, buf := out.writeLoc(int64(n))
	copy(buf[pos:], v)
	out.off += int64(n)
	return n, nil
}

func (out *OutBuf) Write8(v uint8) {
	pos, buf := out.writeLoc(1)
	buf[pos] = v
	out.off++
}

// WriteByte是Write8的别名，用于实现io.ByteWriter接口。
func (out *OutBuf) WriteByte(v byte) error {
	out.Write8(v)
	return nil
}

func (out *OutBuf) Write16(v uint16) {
	out.arch.ByteOrder.PutUint16(out.encbuf[:], v)
	out.Write(out.encbuf[:2])
}

func (out *OutBuf) Write32(v uint32) {
	out.arch.ByteOrder.PutUint32(out.encbuf[:], v)
	out.Write(out.encbuf[:4])
}

func (out *OutBuf) Write32b(v uint32) {
	binary.BigEndian.PutUint32(out.encbuf[:], v)
	out.Write(out.encbuf[:4])
}

func (out *OutBuf) Write64(v uint64) {
	out.arch.ByteOrder.PutUint64(out.encbuf[:], v)
	out.Write(out.encbuf[:8])
}

func (out *OutBuf) Write64b(v uint64) {
	binary.BigEndian.PutUint64(out.encbuf[:], v)
	out.Write(out.encbuf[:8])
}

func (out *OutBuf) WriteString(s string) {
	pos, buf := out.writeLoc(int64(len(s)))
	n := copy(buf[pos:], s)
	if n != len(s) {
		log.Fatalf("WriteString truncated. buffer size: %d, offset: %d, len(s)=%d", len(out.buf), out.off, len(s))
	}
	out.off += int64(n)
}

// WriteStringN写入s的前n个字节。
// 如果n大于len（s），则用零字节填充。
func (out *OutBuf) WriteStringN(s string, n int) {
	out.WriteStringPad(s, n, zeros[:])
}

// WriteStringPad写入s的前n个字节。
// 若n大于len（s），则用pad中的字节填充它（根据需要重复）。
func (out *OutBuf) WriteStringPad(s string, n int, pad []byte) {
	if len(s) >= n {
		out.WriteString(s[:n])
	} else {
		out.WriteString(s)
		n -= len(s)
		for n > len(pad) {
			out.Write(pad)
			n -= len(pad)

		}
		out.Write(pad[:n])
	}
}

// WriteSym写入符号的内容，并返回输出缓冲区
// 我们刚刚写的，所以我们可以对符号内容进行进一步的编辑。
// 对于生成器符号，它还将符号的数据设置为输出
// 缓冲器
func (out *OutBuf) WriteSym(ldr *loader.Loader, s loader.Sym) []byte {
	if !ldr.IsGeneratedSym(s) {
		P := ldr.Data(s)
		n := int64(len(P))
		pos, buf := out.writeLoc(n)
		copy(buf[pos:], P)
		out.off += n
		ldr.FreeData(s)
		return buf[pos : pos+n]
	} else {
		n := ldr.SymSize(s)
		pos, buf := out.writeLoc(n)
		out.off += n
		ldr.MakeSymbolUpdater(s).SetData(buf[pos : pos+n])
		return buf[pos : pos+n]
	}
}
